//  http://code.google.com/p/opposablethumbs

#ifdef __APPLE_CC__
  #include <OpenGL/gl.h>
  #include <OpenGL/glu.h>
  #include <GLUT/glut.h>
#endif
  #ifdef _WIN32
  #include <windows.h>
  #include <GL/OPENglut.h>
#endif

#include <cmath>
#include <vector>


class XY
{
public:
  float m_x, m_y;
};

static float g_mouse_x;
static float g_mouse_y;
static int g_screen_w = 640;
static int g_screen_h = 480;
static float g_aspect_ratio = 1.0f;
static int g_approximate_frame_msec = 10;
static const int g_smiley_max= 250;
static const int g_xy_max = 20;
static XY g_xy[g_xy_max];   // coords of a circle


void DrawSmiley(float x,float y);


class Random
{
  // Detmar's Random class
public:
  Random() {}
  int Int() { return ( Make15Bits() << 17) ^ ( Make15Bits() << 2) ^ Make15Bits(); }
  int Int(int limit)
  {
    if (limit < 2) return 0;
    if (limit <= 0x7fff) return (Make15Bits() * limit) >> 15;
    return(int)((unsigned int)Int()%(unsigned int)limit);
  }
  int Int(int min, int max) { return Int(max - min) + min; }
  float Float() { return Float(1.0f); }
  float Float(float limit) { return limit <= 0.0f ? 0.0f : Make15Bits() * limit / 0x7fff; }
  float Float(float min, float max) { return Float(max - min) + min; }
  void SetSeed(int seed) { m_seed = seed; };
  int	 GetSeed() { return m_seed; };
  void SetSeedAndScramble(int seed) { SetSeed(seed); SetSeed(Int()); Int(); }
private:
  int Make15Bits() { int n = (m_seed >> 16) & 0x7fff; m_seed = m_seed * 0x343fd + 0x269ec3; return n; }
  static int m_seed;
};
int Random::m_seed = 0x12345678;


class Smiley
{
public:
  Smiley() : m_age(0) {}
  void Tick()
  {
    if (m_age > 5)
    {
      Random r;
      m_x += r.Float(-0.002f, 0.002f);
      m_y += r.Float(-0.002f, 0.002f);
    }
    m_age++;
  }
  void Draw()
  {
    DrawSmiley(m_x, m_y);
  }
  int m_age;
  float m_x;
  float m_y;
  int m_argb_color;
  float m_smile;
};


static std::vector<Smiley> g_smileys;


void Tick(int data)
{
  glutTimerFunc(g_approximate_frame_msec, Tick, 0);

  // add a smiley
  if ((int)g_smileys.size() == g_smiley_max)
  {
    g_smileys.erase(g_smileys.begin());
  }
  Smiley s;
  s.m_x = g_mouse_x;
  s.m_y = g_mouse_y;
  g_smileys.push_back(s);

  // update all smileys
  int i;
  for (i = 0; i < (int)g_smileys.size(); i++)
  {
    g_smileys[i].Tick();
  }

  glutPostRedisplay();
}


void Init()
{
  int i;
  for (i = 0; i < g_xy_max; i++)
  {
    float a = i * (2.0f * 3.14159265f / g_xy_max);
    g_xy[i].m_x = sin(a);
    g_xy[i].m_y = -cos(a);
  }

  // get things started
  Tick(0);
}


void MouseMove(int x, int y)
{
  float scale;

  if (g_aspect_ratio >= 1.0f)
  {
    y += (g_screen_w - g_screen_h) / 2;
    scale = 1.0f / g_screen_w;
  }
  else
  {
    x += (g_screen_h - g_screen_w) / 2;
    scale = 1.0f / g_screen_h;
  }

  g_mouse_x = x * scale;
  g_mouse_y = y * scale;
}


void DrawSmiley(float x, float y)
{
  float r = 0.05f;
  int i;
  // draw a disk
  glColor3ub(255, 255, 0);
  glBegin(GL_TRIANGLE_FAN);
  glVertex2f(x, y);
  for (i = 0; i < g_xy_max; i++)
  {
    glVertex2f(x + g_xy[i].m_x * r, y + g_xy[i].m_y * r);
  }
  glVertex2f(x + g_xy[0].m_x * r, y + g_xy[0].m_y * r);
  glEnd();
  // draw a circle outline
  glColor3ub(0, 0, 0);
  glBegin(GL_LINE_LOOP);
  for (i = 0; i < g_xy_max; i++)
  {
    glVertex2f(x + g_xy[i].m_x * r, y + g_xy[i].m_y * r);
  }
  glEnd();
  // eyes
  glBegin(GL_LINES);
  glVertex2f(x + -0.3f * r, y + -0.5f * r);
  glVertex2f(x + -0.3f * r, y + -0.3f * r);
  glVertex2f(x +  0.3f * r, y + -0.5f * r);
  glVertex2f(x +  0.3f * r, y + -0.3f * r);
  glEnd();
  // smile
  int a = g_xy_max / 2 - g_xy_max / 5;
  int b = g_xy_max / 2 + g_xy_max / 5;
  glBegin(GL_LINE_STRIP);
  for (i = a; i <= b; i++)
  {
    glVertex2f(x + g_xy[i].m_x * r * 0.6f, y + g_xy[i].m_y * r * 0.6f);
  }
  glEnd();
}


void Display(void)
{
  // draw simleys
  glClearColor(0.5f, 0.3f, 0.1f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH);
  int i;
  for (i = 0; i < (int)g_smileys.size(); i++)
  {
    g_smileys[i].Draw();
  }

  // done with the game loop
  glutSwapBuffers();
}


void Reshape(int w, int h)
{
  // We want some consistent screen coordinates.  So here we set up the screen
  // size so that it fits within a unit square.  The upper left of the square
  // is set to (0, 0), and the lower right to (1, 1).  Note that the square's
  // corners are always outside the visible screen (unless the screen is square).
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  float fov = 60.0f;
  w = w < 1 ? 1 : w;
  h = h < 1 ? 1 : h;

  g_screen_w = w;
  g_screen_h = h;

  g_aspect_ratio = (float)w / h;
  if (g_aspect_ratio >= 1.0f)
  {
    // screen is wide-ish
    float pad = (1.0f - (1.0f / g_aspect_ratio)) * 0.5f;   // stuff to keep the pixels square
    glOrtho(0.0f, 1.0f, 1.0f - pad, pad, 0.1f, 100.0f);
  }
  else
  {
    // screen is tallish
    float pad = (1.0f - g_aspect_ratio) * 0.5f;   // stuff to keep the pixels square
    glOrtho(pad, 1.0f - pad, 1.0f, 0.0f, 0.1f, 100.0f);
  }
  glTranslatef(0.0f, 0.0f, -0.2f);  // this is so Z=0 objects will not be culled
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

                       
int main(int argc, char **argv)
{
#ifdef _WIN32
  // If you run this program from Windows (not a console), then two windows
  // are opened: the opengl window, and a text console.  This call
  // closes the text console.
  FreeConsole();
#endif
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutCreateWindow("Chinchilla");
  Init();
  glutDisplayFunc(Display);
  glutReshapeFunc(Reshape);
  glutReshapeWindow(g_screen_w, g_screen_h);
  glutMotionFunc(MouseMove);
  glutPassiveMotionFunc(MouseMove);
  glutMainLoop();
  return 0;
}
